package ccm.common;

import java.awt.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class CCMConfigManager
{
    private static final String regex="[./]";
    private static final ConcurrentHashSet<String> modify=new ConcurrentHashSet<String>();
    private static ConcurrentHashMap<String,Object> config;

    static
    {
        try
        {
            ObjectInputStream objectInputStream=new ObjectInputStream(Files.newInputStream(Paths.get(getConfigFile())));
            Object tmp=objectInputStream.readObject();
            if(tmp instanceof ConcurrentHashMap)
            {
                config=(ConcurrentHashMap<String,Object>)tmp;
                System.out.println("load successful");
            }
        }catch(IOException|ClassNotFoundException ignored)
        {
        }
        if(config==null)
        {
            config=new ConcurrentHashMap<>();
        }
        print();
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(CCMConfigManager::save,0,10,TimeUnit.SECONDS);
        Runtime.getRuntime().addShutdownHook(new Thread(CCMConfigManager::save));
        CCMConfigManager.setFontIfAbsent("ccm/general/font",new Font("楷体",Font.PLAIN,15));
    }

    private CCMConfigManager()
    {
    }

    private static Object setObject(String key,Object value) throws NoConfigException
    {
        return setObject(key.split(regex),value);
    }

    private static Object setObject(String[] ks,Object v) throws NoConfigException
    {
        ConcurrentHashMap<String,Object> obj=__setObject(ks,v);
        Object tmp=obj.get(ks[ks.length-1]);
        obj.put(ks[ks.length-1],v);
        if(!Objects.deepEquals(tmp,v))
        {
            modify.add(String.join(".",ks));
        }
        return obj.get(ks[ks.length-1]);
    }

    private static ConcurrentHashMap<String,Object> __setObject(String[] ks,Object v) throws NoConfigException
    {
        if(!(v instanceof Serializable))
        {
            throw new NoConfigException();
        }
        if(ks.length==0)
        {
            throw new NoConfigException();
        }
        ConcurrentHashMap<String,Object> obj=config;
        for(int i=0;i<ks.length-1;++i)
        {
            Object tmp=obj.get(ks[i]);
            if(tmp instanceof ConcurrentHashMap)
            {
                obj=(ConcurrentHashMap<String,Object>)tmp;
            }
            else if(tmp==null)
            {
                obj.put(ks[i],tmp=new ConcurrentHashMap<>());
                obj=(ConcurrentHashMap<String,Object>)tmp;
            }
            else
            {
                throw new NoConfigException();
            }
        }
        //System.out.println(Arrays.toString(Thread.currentThread().getStackTrace()));
        return obj;
    }

    private static Object setObjectIfAbsent(String key,Object value) throws NoConfigException
    {
        return setObjectIfAbsent(key.split(regex),value);
    }

    private static Object setObjectIfAbsent(String[] ks,Object v) throws NoConfigException
    {
        ConcurrentHashMap<String,Object> obj=__setObject(ks,v);
        Object tmp=obj.get(ks[ks.length-1]);
        obj.putIfAbsent(ks[ks.length-1],v);
        if(!Objects.deepEquals(tmp,v))
        {
            modify.add(String.join(".",ks));
        }
        return obj.get(ks[ks.length-1]);
    }

    private static Object getObject(String key) throws NoConfigException
    {
        return getObject(key.split(regex));
    }

    private static Object getObject(String[] ks) throws NoConfigException
    {
        if(ks.length==0)
        {
            throw new NoConfigException();
        }
        ConcurrentHashMap<String,Object> obj=config;
        for(int i=0;i<ks.length-1;++i)
        {
            Object tmp=obj.get(ks[i]);
            if(tmp instanceof ConcurrentHashMap)
            {
                obj=(ConcurrentHashMap<String,Object>)tmp;
            }
            else
            {
                throw new NoConfigException(ks[i]+i+Arrays.toString(ks));
            }
        }
        Object tmp=obj.get(ks[ks.length-1]);
        if(tmp==null)
        {
            throw new NoConfigException();
        }
        return tmp;
    }

    public static int setInteger(String key,int value) throws NoConfigException
    {
        return setInteger(key.split(regex),value);
    }

    public static int setInteger(String[] ks,int v) throws NoConfigException
    {
        return (int)setObject(ks,v);
    }

    public static int setIntegerIfAbsent(String key,int value) throws NoConfigException
    {
        return setIntegerIfAbsent(key.split(regex),value);
    }

    public static int setIntegerIfAbsent(String[] ks,int v) throws NoConfigException
    {
        try
        {
            return (int)setObjectIfAbsent(ks,v);
        }catch(ClassCastException ignore)
        {
            return (int)setObject(ks,v);
        }
    }

    public static Number setNumber(String key,Number value) throws NoConfigException
    {
        return setNumber(key.split(regex),value);
    }

    public static Number setNumber(String[] ks,Number v) throws NoConfigException
    {
        return (Number)setObject(ks,v);
    }

    public static Number setNumberIfAbsent(String key,Number value) throws NoConfigException
    {
        return setNumberIfAbsent(key.split(regex),value);
    }

    public static Number setNumberIfAbsent(String[] ks,Number v) throws NoConfigException
    {
        try
        {
            return (Number)setObjectIfAbsent(ks,v);
        }catch(ClassCastException ignore)
        {
            return (Number)setObject(ks,v);
        }
    }

    public static Number getNumber(String key) throws NoConfigException
    {
        return getNumber(key.split(regex));
    }

    public static Number getNumber(String[] ks) throws NoConfigException
    {
        Object obj=getObject(ks);
        if(!(obj instanceof Number))
        {
            throw new NoConfigException();
        }
        return (Number)obj;
    }

    public static boolean setBoolean(String key,boolean value) throws NoConfigException
    {
        return setBoolean(key.split(regex),value);
    }

    public static boolean setBoolean(String[] ks,boolean v) throws NoConfigException
    {
        return (boolean)setObject(ks,v);
    }

    public static boolean setBooleanIfAbsent(String key,boolean value) throws NoConfigException
    {
        return setBooleanIfAbsent(key.split(regex),value);
    }

    public static boolean setBooleanIfAbsent(String[] ks,boolean v) throws NoConfigException
    {
        try
        {
            return (boolean)setObjectIfAbsent(ks,v);
        }catch(ClassCastException ignore)
        {
            return (boolean)setObject(ks,v);
        }
    }

    public static boolean getBoolean(String key) throws NoConfigException
    {
        return getBoolean(key.split(regex));
    }

    public static boolean getBoolean(String[] ks) throws NoConfigException
    {
        Object obj=getObject(ks);
        if(!(obj instanceof Boolean))
        {
            throw new NoConfigException();
        }
        return (boolean)obj;
    }

    public static float setFloat(String key,float value) throws NoConfigException
    {
        return setFloat(key.split(regex),value);
    }

    public static float setFloat(String[] ks,float v) throws NoConfigException
    {
        return (float)setObject(ks,v);
    }

    public static float setFloatIfAbsent(String key,float value) throws NoConfigException
    {
        return setFloatIfAbsent(key.split(regex),value);
    }

    public static float setFloatIfAbsent(String[] ks,float v) throws NoConfigException
    {
        try
        {
            return (float)setObjectIfAbsent(ks,v);
        }catch(ClassCastException ignore)
        {
            return (float)setObject(ks,v);
        }
    }

    public static float getFloat(String key) throws NoConfigException
    {
        return getFloat(key.split(regex));
    }

    public static float getFloat(String[] ks) throws NoConfigException
    {
        Object obj=getObject(ks);
        if(!(obj instanceof Float))
        {
            throw new NoConfigException();
        }
        return (float)obj;
    }

    public static long setLong(String key,long value) throws NoConfigException
    {
        return setLong(key.split(regex),value);
    }

    public static long setLong(String[] ks,long v) throws NoConfigException
    {
        return (long)setObject(ks,v);
    }

    public static long setLongIfAbsent(String key,long value) throws NoConfigException
    {
        return setLongIfAbsent(key.split(regex),value);
    }

    public static long setLongIfAbsent(String[] ks,long v) throws NoConfigException
    {
        try
        {
            return (long)setObjectIfAbsent(ks,v);
        }catch(ClassCastException ignore)
        {
            return (long)setObject(ks,v);
        }
    }

    public static long getLong(String key) throws NoConfigException
    {
        return getLong(key.split(regex));
    }

    public static long getLong(String[] ks) throws NoConfigException
    {
        Object obj=getObject(ks);
        if(!(obj instanceof Long))
        {
            throw new NoConfigException();
        }
        return (long)obj;
    }

    public static String setString(String key,String value) throws NoConfigException
    {
        return setString(key.split(regex),value);
    }

    public static String setString(String[] ks,String v) throws NoConfigException
    {
        return (String)setObject(ks,v);
    }

    public static String setStringIfAbsent(String key,String value) throws NoConfigException
    {
        return setStringIfAbsent(key.split(regex),value);
    }

    public static String setStringIfAbsent(String[] ks,String v) throws NoConfigException
    {
        try
        {
            return (String)setObjectIfAbsent(ks,v);
        }catch(ClassCastException ignore)
        {
            return (String)setObject(ks,v);
        }
    }

    public static String getString(String key) throws NoConfigException
    {
        return getString(key.split(regex));
    }

    public static String getString(String[] ks) throws NoConfigException
    {
        Object obj=getObject(ks);
        if(!(obj instanceof String))
        {
            throw new NoConfigException();
        }
        return (String)obj;
    }

    public static Color setColor(String key,Color value) throws NoConfigException
    {
        return setColor(key.split(regex),value);
    }

    public static Color setColor(String[] ks,Color v) throws NoConfigException
    {
        return (Color)setObject(ks,v);
    }

    public static Color setColorIfAbsent(String key,Color value) throws NoConfigException
    {
        return setColorIfAbsent(key.split(regex),value);
    }

    public static Color setColorIfAbsent(String[] ks,Color v) throws NoConfigException
    {
        try
        {
            return (Color)setObjectIfAbsent(ks,v);
        }catch(ClassCastException ignore)
        {
            return (Color)setObject(ks,v);
        }
    }

    public static Color getColor(String key) throws NoConfigException
    {
        return getColor(key.split(regex));
    }

    public static Color getColor(String[] ks) throws NoConfigException
    {
        Object obj=getObject(ks);
        if(!(obj instanceof Color))
        {
            throw new NoConfigException();
        }
        return (Color)obj;
    }

    public static void setFont(String key,Font value) throws NoConfigException
    {
        setFont(key.split(regex),value);
    }

    public static void setFont(String[] ks,Font v) throws NoConfigException
    {
        setObject(ks,v);
    }

    public static void setFontIfAbsent(String key,Font value) throws NoConfigException
    {
        setFontIfAbsent(key.split(regex),value);
    }

    public static void setFontIfAbsent(String[] ks,Font v) throws NoConfigException
    {
        setObjectIfAbsent(ks,v);
    }

    public static Font getFont(String key) throws NoConfigException
    {
        return getFont(key.split(regex));
    }

    public static Font getFont(String[] ks) throws NoConfigException
    {
        return getFont(ks,true);
    }

    public static Font getFont(String key,boolean withResize) throws NoConfigException
    {
        return getFont(key.split(regex),withResize);
    }

    public static Font getFont(String[] ks,boolean withResize) throws NoConfigException
    {
        Object obj=getObject(ks);
        if(!(obj instanceof Font))
        {
            throw new NoConfigException();
        }
        if(withResize)
        {
            obj=new Font(((Font)obj).getName(),((Font)obj).getStyle(),(int)(((Font)obj).getSize()*SystemParametersInfo.getFontRate()));
        }
        return (Font)obj;
    }

    public static void save()
    {
        try
        {
            if(!modify.isEmpty())
            {
                System.out.println("save start");
                boolean save=false;
                try
                {
                    ObjectInputStream objectInputStream=new ObjectInputStream(Files.newInputStream(Paths.get(getConfigFile())));
                    Object tmp=objectInputStream.readObject();
                    if(tmp instanceof ConcurrentHashMap)
                    {
                        ConcurrentHashMap<String,Object> loadedConfig=(ConcurrentHashMap<String,Object>)tmp;
                        for(String k: modify)
                        {
                            String[] ks=k.split(regex);
                            Object v=getObject(ks);
                            System.out.println("save "+Arrays.toString(ks)+" "+v);
                            ConcurrentHashMap<String,Object> obj=loadedConfig;
                            for(int i=0;i<ks.length-1;++i)
                            {
                                Object aaa=obj.get(ks[i]);
                                if(aaa instanceof ConcurrentHashMap)
                                {
                                    obj=(ConcurrentHashMap<String,Object>)aaa;
                                }
                                else
                                {
                                    throw new ClassNotFoundException();
                                }
                            }
                            obj.put(ks[ks.length-1],v);
                        }
                        ObjectOutputStream objectOutputStream=new ObjectOutputStream(Files.newOutputStream(Paths.get(getConfigFile())));
                        objectOutputStream.writeObject(loadedConfig);
                        objectOutputStream.close();
                        save=true;
                        config=loadedConfig;
                    }
                }catch(Exception ex)
                {
                    ex.printStackTrace();
                }
                if(!save)
                {
                    ObjectOutputStream objectOutputStream=new ObjectOutputStream(Files.newOutputStream(Paths.get(getConfigFile())));
                    objectOutputStream.writeObject(config);
                    objectOutputStream.close();
                    System.out.println("write all");
                }
                modify.clear();
                System.out.println("save successful");
            }
            else
            {
                System.out.println("save no need");
            }
        }catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    public static String getConfigFile()
    {
        return getConfigDir()+"/CCMConfigManager.ccmconfig";
    }

    public static String getConfigDir()
    {
        return System.getProperty("user.home");
    }

    public static void main(String[] args) throws NoConfigException
    {
        print();
        System.out.println(CCMConfigManager.getInteger("ui.font.size"));
    }

    public static void print()
    {
        System.out.println(config);
    }

    public static int getInteger(String key) throws NoConfigException
    {
        return getInteger(key.split(regex));
    }

    public static int getInteger(String[] ks) throws NoConfigException
    {
        Object obj=getObject(ks);
        if(!(obj instanceof Integer))
        {
            throw new NoConfigException();
        }
        return (int)obj;
    }
}
